/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com)  |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/

#include "hphp/runtime/vm/jit/irlower-internal.h"

#include "hphp/runtime/base/tv-arith.h"
#include "hphp/runtime/vm/hhbc.h"

#include "hphp/runtime/vm/jit/call-spec.h"
#include "hphp/runtime/vm/jit/code-gen-cf.h"
#include "hphp/runtime/vm/jit/extra-data.h"
#include "hphp/runtime/vm/jit/ir-instruction.h"
#include "hphp/runtime/vm/jit/ssa-tmp.h"
#include "hphp/runtime/vm/jit/vasm-gen.h"
#include "hphp/runtime/vm/jit/vasm-instr.h"
#include "hphp/runtime/vm/jit/vasm-reg.h"

#include "hphp/util/asm-x64.h"
#include "hphp/util/trace.h"

namespace HPHP { namespace jit { namespace irlower {

TRACE_SET_MOD(irlower);

///////////////////////////////////////////////////////////////////////////////

namespace {

///////////////////////////////////////////////////////////////////////////////

template<class vinst>
void implUnop(Vout& v, IRLS& env, const IRInstruction* inst) {
  auto const s = srcLoc(env, inst, 0).reg();
  auto const d = dstLoc(env, inst, 0).reg();

  v << vinst{s, d};
}

template<class vinst>
void implBinop(Vout& v, IRLS& env, const IRInstruction* inst) {
  auto const s0 = srcLoc(env, inst, 0).reg();
  auto const s1 = srcLoc(env, inst, 1).reg();
  auto const d  = dstLoc(env, inst, 0).reg();

  v << vinst{s1, s0, d};
}

template<class vinst>
Vreg implBinopSF(Vout& v, IRLS& env, const IRInstruction* inst) {
  auto const s0 = srcLoc(env, inst, 0).reg();
  auto const s1 = srcLoc(env, inst, 1).reg();
  auto const d  = dstLoc(env, inst, 0).reg();
  auto const sf = v.makeReg();

  v << vinst{s1, s0, d, sf};
  return sf;
}

template<class vinst>
void implArithO(Vout& v, IRLS& env, const IRInstruction* inst) {
  auto const sf = implBinopSF<vinst>(v, env, inst);
  v << jcc{CC_O, sf, {label(env, inst->next()), label(env, inst->taken())}};
}

template<RoundDirection rd>
void implRound(Vout& v, IRLS& env, const IRInstruction* inst) {
  auto const s = srcLoc(env, inst, 0).reg();
  auto const d = dstLoc(env, inst, 0).reg();
  v << roundsd{rd, s, d};
}

template<class vinst, class vinsti>
void implShift(Vout& v, IRLS& env, const IRInstruction* inst) {
  auto const shift = inst->src(1);
  auto const s0 = srcLoc(env, inst, 0).reg();  // bytes to be shifted
  auto const s1 = srcLoc(env, inst, 1).reg();  // shift amount
  auto const d  = dstLoc(env, inst, 0).reg();
  auto const sf = v.makeReg();

  if (shift->hasConstVal()) {
    int n = shift->intVal() & 0x3f; // only use low 6 bits.
    v << vinsti{n, s0, d, sf};
  } else {
    v << vinst{s1, s0, d, sf};
  }
}

///////////////////////////////////////////////////////////////////////////////

}

///////////////////////////////////////////////////////////////////////////////

#define ARITH_OPS               \
  AO(AddInt,  BinopSF,  addq)   \
  AO(SubInt,  BinopSF,  subq)   \
  AO(MulInt,  BinopSF,  imul)   \
  AO(AddIntO, ArithO,   addq)   \
  AO(SubIntO, ArithO,   subq)   \
  AO(MulIntO, ArithO,   imul)   \
  AO(AddDbl,  Binop,    addsd)  \
  AO(SubDbl,  Binop,    subsd)  \
  AO(MulDbl,  Binop,    mulsd)  \
  AO(AbsDbl,  Unop,     absdbl) \
  AO(Sqrt,    Unop,     sqrtsd) \
  AO(AndInt,  BinopSF,  andq)   \
  AO(OrInt,   BinopSF,  orq)    \
  AO(XorInt,  BinopSF,  xorq)   \
  AO(XorBool, BinopSF,  xorb)   \

#define AO(Inst, Impl, vinst)                           \
  void cg##Inst(IRLS& env, const IRInstruction* inst) { \
    impl##Impl<vinst>(vmain(env), env, inst);           \
  }
ARITH_OPS
#undef AO

#undef ARITH_OPS

///////////////////////////////////////////////////////////////////////////////

void cgFloor(IRLS& env, const IRInstruction* inst) {
  implRound<RoundDirection::floor>(vmain(env), env, inst);
}
void cgCeil(IRLS& env, const IRInstruction* inst) {
  implRound<RoundDirection::ceil>(vmain(env), env, inst);
}

void cgShl(IRLS& env, const IRInstruction* inst) {
  implShift<shl,shlqi>(vmain(env), env, inst);
}
void cgShr(IRLS& env, const IRInstruction* inst) {
  implShift<sar,sarqi>(vmain(env), env, inst);
}
void cgLshr(IRLS& env, const IRInstruction* inst) {
  implShift<shr,shrqi>(vmain(env), env, inst);
}

void cgDivDbl(IRLS& env, const IRInstruction* inst) {
  auto const d = dstLoc(env, inst, 0).reg();
  auto const dividend = srcLoc(env, inst, 0).reg();
  auto const divisor  = srcLoc(env, inst, 1).reg();
  auto& v = vmain(env);

  v << divsd{divisor, dividend, d};
}

void cgDivInt(IRLS& env, const IRInstruction* inst) {
  auto const d = dstLoc(env, inst, 0).reg();
  auto const dividend = srcLoc(env, inst, 0).reg();
  auto const divisor  = srcLoc(env, inst, 1).reg();
  auto& v = vmain(env);

  v << divint{dividend, divisor, d};
}

void cgMod(IRLS& env, const IRInstruction* inst) {
  auto const d = dstLoc(env, inst, 0).reg();
  auto const dividend = srcLoc(env, inst, 0).reg();
  auto const divisor  = srcLoc(env, inst, 1).reg();
  auto& v = vmain(env);

  v << srem{dividend, divisor, d};
}

///////////////////////////////////////////////////////////////////////////////

namespace {

auto setOpOpToHelper(SetOpOp op) {
  switch (op) {
    case SetOpOp::PlusEqual:   return cellAddEq;
    case SetOpOp::MinusEqual:  return cellSubEq;
    case SetOpOp::MulEqual:    return cellMulEq;
    case SetOpOp::ConcatEqual: return cellConcatEq;
    case SetOpOp::DivEqual:    return cellDivEq;
    case SetOpOp::PowEqual:    return cellPowEq;
    case SetOpOp::ModEqual:    return cellModEq;
    case SetOpOp::AndEqual:    return cellBitAndEq;
    case SetOpOp::OrEqual:     return cellBitOrEq;
    case SetOpOp::XorEqual:    return cellBitXorEq;
    case SetOpOp::SlEqual:     return cellShlEq;
    case SetOpOp::SrEqual:     return cellShrEq;
    case SetOpOp::PlusEqualO:  return cellAddEqO;
    case SetOpOp::MinusEqualO: return cellSubEqO;
    case SetOpOp::MulEqualO:   return cellMulEqO;
  }
  not_reached();
}

}

void cgSetOpCell(IRLS& env, const IRInstruction* inst) {
  auto const op = inst->extra<SetOpData>()->op;
  auto const helper = setOpOpToHelper(op);
  auto& v = vmain(env);
  cgCallHelper(v, env, CallSpec::direct(helper), kVoidDest, SyncOptions::Sync,
               argGroup(env, inst).ssa(0).typedValue(1));
}

template <SetOpOp Op>
static void setOpCellVerifyImpl(tv_lval lhs, Cell rhs,
                                const Class* cls, Slot slot) {
  assertx(RuntimeOption::EvalCheckPropTypeHints > 0);
  assertx(slot < cls->numDeclProperties());
  auto const& prop = cls->declProperties()[slot];
  assertx(prop.typeConstraint.isCheckable());

  if (setOpNeedsTypeCheck(prop.typeConstraint, Op, lhs)) {
    /*
     * If this property has a type-hint, we can't do the setop truely in
     * place. We need to verify that the new value satisfies the type-hint
     * before assigning back to the property (if we raise a warning and throw,
     * we don't want to have already put the value into the prop).
     */
    Cell temp;
    cellDup(*lhs, temp);
    SCOPE_FAIL { tvDecRefGen(&temp); };
    setOpOpToHelper(Op)(&temp, rhs);
    prop.typeConstraint.verifyProperty(&temp, cls, prop.cls, prop.name);
    cellMove(temp, lhs);
  } else {
    setOpOpToHelper(Op)(lhs, rhs);
  }
}

void cgSetOpCellVerify(IRLS& env, const IRInstruction* inst) {
  auto const op = inst->extra<SetOpData>()->op;
  auto& v = vmain(env);

  using S = SetOpOp;
  auto const helper = [&]{
    switch (op) {
      case S::PlusEqual:   return setOpCellVerifyImpl<S::PlusEqual>;
      case S::MinusEqual:  return setOpCellVerifyImpl<S::MinusEqual>;
      case S::MulEqual:    return setOpCellVerifyImpl<S::MulEqual>;
      case S::ConcatEqual: return setOpCellVerifyImpl<S::ConcatEqual>;
      case S::DivEqual:    return setOpCellVerifyImpl<S::DivEqual>;
      case S::PowEqual:    return setOpCellVerifyImpl<S::PowEqual>;
      case S::ModEqual:    return setOpCellVerifyImpl<S::ModEqual>;
      case S::AndEqual:    return setOpCellVerifyImpl<S::AndEqual>;
      case S::OrEqual:     return setOpCellVerifyImpl<S::OrEqual>;
      case S::XorEqual:    return setOpCellVerifyImpl<S::XorEqual>;
      case S::SlEqual:     return setOpCellVerifyImpl<S::SlEqual>;
      case S::SrEqual:     return setOpCellVerifyImpl<S::SrEqual>;
      case S::PlusEqualO:  return setOpCellVerifyImpl<S::PlusEqualO>;
      case S::MinusEqualO: return setOpCellVerifyImpl<S::MinusEqualO>;
      case S::MulEqualO:   return setOpCellVerifyImpl<S::MulEqualO>;
    }
    not_reached();
  }();

  cgCallHelper(
    v,
    env,
    CallSpec::direct(helper),
    kVoidDest,
    SyncOptions::Sync,
    argGroup(env, inst)
      .ssa(0)
      .typedValue(1)
      .ssa(2)
      .ssa(3)
  );
}

///////////////////////////////////////////////////////////////////////////////

}}}
